using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using InfiniTec;
using InfiniTec.Diagnostics;
using InfiniTec.Exchange.Notifications;
using JetBrains.Annotations;
using Microsoft.Exchange.WebServices.Data;
using EventType = Microsoft.Exchange.WebServices.Data.EventType;
using ExchangeVersion = Microsoft.Exchange.WebServices.Data.ExchangeVersion;

namespace CalendarSubscriptionApp
{
	public sealed class ExchangeConnector : IExchangeWebServicesAdapter
	{
		private static readonly Trace Trace = new Trace("InfiniTec.Samples.PushNotificationClient");

		private static readonly PropertySet _DefaultAppointmentPropertySet = new PropertySet(AppointmentSchema.IsCancelled)
		                                                                     {
		                                                                     	BasePropertySet =
		                                                                     		BasePropertySet.FirstClassProperties
		                                                                     };

		private static NetworkCredential _Credential = CredentialCache.DefaultNetworkCredentials;

		private readonly Dictionary<string, Uri> _EmailAddressToServerMapping =
			new Dictionary<string, Uri>(StringComparer.OrdinalIgnoreCase);

		private readonly object _EmailAddressToServerMappingLock = new object();

		private readonly Dictionary<string, PullSubscription> _SubscriptionMapping =
			new Dictionary<string, PullSubscription>();

		[PublicAPI]
		public static NetworkCredential Credential
		{
			get { return _Credential; }
			set { _Credential = value; }
		}

		#region IExchangeWebServicesAdapter Members

		SubscriptionInfo IExchangeWebServicesAdapter.Subscribe(IEnumerable<FolderReference> folders, EventTypes events,
		                                                       string lastKnownWatermark,
		                                                       int timeoutIntervalInMinutes, Uri localEndpoint)
		{
			if (folders == null) throw new ArgumentNullException("folders");
			folders = folders.Execute();
			if (!folders.Any())
			{
				throw new ArgumentException("The folders enumeration musts not be empty.", "folders");
			}

			var service = GetExchangeService(folders.First().Mailbox.Address);
			var response =
				service.SubscribeToPushNotifications(folders.Select(ConvertToFolderId),
				                                     localEndpoint, timeoutIntervalInMinutes, lastKnownWatermark,
				                                     ToManageApiEventTypeArray(events));

			return new SubscriptionInfo(response.Id, response.Watermark);
		}

		SubscriptionInfo IExchangeWebServicesAdapter.Subscribe(IEnumerable<FolderReference> folders, EventTypes events,
		                                                       string lastKnownWatermark, int timeoutIntervalInMinutes)
		{
			if (folders == null) throw new ArgumentNullException("folders");

			folders = folders.Execute();
			if (!folders.Any())
			{
				throw new ArgumentException("The folders enumeration musts not be empty.", "folders");
			}

			var emailAddress = folders.First().Mailbox.Address;
			var service = GetExchangeService(emailAddress);
			var subscription =
				service.SubscribeToPullNotifications(folders.Select(ConvertToFolderId),
				                                     timeoutIntervalInMinutes,
				                                     lastKnownWatermark, ToManageApiEventTypeArray(events));

			_SubscriptionMapping[subscription.Id] = subscription;
			return new SubscriptionInfo(subscription.Id, subscription.Watermark);
		}

		void IExchangeWebServicesAdapter.Unsubscribe(string subscriptionId)
		{
			var subscription = GetSubscriptionBySubscriptionId(subscriptionId);
			subscription.Unsubscribe();
		}

		IEnumerable<EventInfo> IExchangeWebServicesAdapter.GetEvents(string subscriptionId, string watermark)
		{
			var subscription = GetSubscriptionBySubscriptionId(subscriptionId);
			var events = subscription.GetEvents();

			return events.AllEvents.Select(ConvertToEventInfo);
		}

		#endregion

		private EventInfo ConvertToEventInfo(NotificationEvent notification)
		{
			/*	var itemEvent = notification as ItemEvent;
    		var folderEvent = notification as FolderEvent;

    		switch (notification.EventType)
    		{
    			case EventType.Copied:
				case EventType.Modified:
					return EventInfo.ForCopyOrMoveEvent(notification.EventType ==EventType.Copied ? EventTypes.Copied : EventTypes.Moved,
						notification.ParentFolderId)
    		}*/

			throw new NotSupportedException();
		}

		private PullSubscription GetSubscriptionBySubscriptionId(string subscriptionId)
		{
			PullSubscription subscription;
			if (!_SubscriptionMapping.TryGetValue(subscriptionId, out subscription))
			{
				throw new InvalidOperationException(string.Format("A subscription with the id {0} could not be found.",
				                                                  subscriptionId));
			}
			return subscription;
		}

		[field: NonSerialized]
		public event EventHandler<AutodiscoverFailedEventArgs> AutodiscoverFailed;

		[field: NonSerialized]
		public event EventHandler<AutodiscoverRedirectedEventArgs> AutoDiscoverRedirected;

		private ExchangeService GetExchangeService(string primaryEmailAddress)
		{
			Credential = CredentialCache.DefaultNetworkCredentials;
			lock (_EmailAddressToServerMappingLock)
			{
				Uri address;
				if (_EmailAddressToServerMapping.TryGetValue(primaryEmailAddress, out address))
				{
					return new ExchangeService(ExchangeVersion.Exchange2007_SP1)
					       {Url = address, Credentials = Credential};
				}
			}

			var result = new ExchangeService(ExchangeVersion.Exchange2007_SP1) {Credentials = Credential};
			Uri ewsUrl;
			try
			{
				result.AutodiscoverUrl(primaryEmailAddress, AutodiscoverValidateRedirectionUrlCallback);
				ewsUrl = result.Url;
			}
			catch (Exception ex)
			{
				var args = new AutodiscoverFailedEventArgs(primaryEmailAddress, ex);
				AutodiscoverFailed.Raise(this, args);
				if (args.ExchangeWebServicesUrl == null)
				{
					throw;
				}
				ewsUrl = args.ExchangeWebServicesUrl;
				result = new ExchangeService(ExchangeVersion.Exchange2007_SP1) {Url = ewsUrl, Credentials = Credential};
			}
			lock (_EmailAddressToServerMappingLock)
			{
				_EmailAddressToServerMapping[primaryEmailAddress] = ewsUrl;
			}
			return result;
		}

		private bool AutodiscoverValidateRedirectionUrlCallback(string url)
		{
			var args = new AutodiscoverRedirectedEventArgs(url);

			AutoDiscoverRedirected.Raise(this, args);
			return args.Cancel;
		}

		private static Mailbox ConvertToManagedApiMailbox(MailboxReference reference)
		{
			if (reference == null) return null;
			return new Mailbox(reference.Address, reference.RoutingType);
		}

		private static FolderId ConvertToFolderId(FolderReference reference)
		{
			if (reference == null) throw new ArgumentNullException("reference");
			if (reference.WellKnownFolderId == null)
			{
				return new FolderId(reference.FolderId);
			}
			switch (reference.WellKnownFolderId.Value)
			{
				case WellKnownFolderId.Calendar:
					return new FolderId(WellKnownFolderName.Calendar, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.DeletedItems:
					return new FolderId(WellKnownFolderName.DeletedItems, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.Contacts:
					return new FolderId(WellKnownFolderName.Contacts, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.Drafts:
					return new FolderId(WellKnownFolderName.Drafts, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.Inbox:
					return new FolderId(WellKnownFolderName.Inbox, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.Journal:
					return new FolderId(WellKnownFolderName.Journal, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.JunkEmail:
					return new FolderId(WellKnownFolderName.JunkEmail, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.MessageFolderRoot:
					return new FolderId(WellKnownFolderName.MsgFolderRoot, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.Notes:
					return new FolderId(WellKnownFolderName.Notes, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.Outbox:
					return new FolderId(WellKnownFolderName.Outbox, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.PublicFoldersRoot:
					return new FolderId(WellKnownFolderName.PublicFoldersRoot,
					                    ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.Root:
					return new FolderId(WellKnownFolderName.Root, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.SearchFolders:
					return new FolderId(WellKnownFolderName.SearchFolders, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.SentItems:
					return new FolderId(WellKnownFolderName.SentItems, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.Tasks:
					return new FolderId(WellKnownFolderName.Tasks, ConvertToManagedApiMailbox(reference.Mailbox));
				case WellKnownFolderId.Voicemail:
					return new FolderId(WellKnownFolderName.VoiceMail, ConvertToManagedApiMailbox(reference.Mailbox));
				default:
					throw new InvalidOperationException(string.Format("The WellknownFolderId {0} is not supported.",
					                                                  reference.WellKnownFolderId.Value));
			}
		}

		private static EventType[] ToManageApiEventTypeArray(EventTypes events)
		{
			var result = new List<EventType>();

			if ((events & EventTypes.Copied) == EventTypes.Copied) result.Add(EventType.Copied);
			if ((events & EventTypes.Created) == EventTypes.Created) result.Add(EventType.Created);
			if ((events & EventTypes.Deleted) == EventTypes.Deleted) result.Add(EventType.Deleted);
			if ((events & EventTypes.Modified) == EventTypes.Modified) result.Add(EventType.Modified);
			if ((events & EventTypes.Moved) == EventTypes.Moved) result.Add(EventType.Moved);
			if ((events & EventTypes.NewMail) == EventTypes.NewMail) result.Add(EventType.NewMail);

			return result.ToArray();
		}

		public IEnumerable<Appointment> FindAppointments(string primaryEmailAddress)
		{
			FindItemsResults<Appointment> result;
			do
			{
				var service = GetExchangeService(primaryEmailAddress);
				result =
					service.FindAppointments(
						new FolderId(WellKnownFolderName.Calendar, new Mailbox(primaryEmailAddress)),
						new CalendarView(DateTime.Today, DateTime.Today.AddDays(7)));
				foreach (var item in result)
				{
					yield return item;
				}
			} while (result.MoreAvailable);
		}

		public Appointment RefreshAppointment(ItemId id, string primaryEmailAddress)
		{
			var service = GetExchangeService(primaryEmailAddress);
			var response = service.BindToItems(id.AsEnumerable(), _DefaultAppointmentPropertySet).Single();
			CheckResponse(response);

			return response.Item as Appointment;
		}

		private static void CheckResponse(ServiceResponse response)
		{
			if (response.Result == ServiceResult.Success) return;
			throw new ExchangeWebServiceCallFailedException(response);
		}

		public FolderId GetDefaultCalendarFolderId(string primaryEmailAddress)
		{
			var service = GetExchangeService(primaryEmailAddress);
			var folder = Folder.Bind(service,
			                         new FolderId(WellKnownFolderName.Calendar, new Mailbox(primaryEmailAddress)));
			return folder.Id;
		}
	}
}